use crate::db::{dao, po};
use crate::dto;
use crate::service::error::Error;
use std::net::Ipv6Addr;
use std::sync::Arc;
use wanmesh_common::error::ResponseError;

#[async_trait::async_trait]
pub trait EndpointSrv6ConfigService: Send + Sync {
    async fn add(
        &self,
        endpoint_id: &str,
        params: &dto::core::endpoint_srv6_config::AddParams,
    ) -> Result<(), Error>;

    async fn get(
        &self,
        endpoint_id: &str,
    ) -> Result<dto::core::endpoint_srv6_config::EndpointSrv6Config, Error>;

    async fn try_get(
        &self,
        endpoint_id: &str,
    ) -> Result<Option<dto::core::endpoint_srv6_config::EndpointSrv6Config>, Error>;
    
    async fn update(
        &self,
        endpoint_id: &str,
        params: &dto::core::endpoint_srv6_config::UpdateParams,
    ) -> Result<(), Error>;

    async fn delete(&self, endpoint_id: &str) -> Result<(), Error>;

    async fn exists(&self, filter: &dto::core::endpoint_srv6_config::Filter)
    -> Result<bool, Error>;
}

pub struct DefaultEndpointSrv6ConfigService {
    endpoint_srv6_config_dao: Arc<dyn dao::endpoint_srv6_config::EndpointSrv6ConfigDao>,
}

impl DefaultEndpointSrv6ConfigService {
    pub fn new(
        endpoint_srv6_config_dao: Arc<dyn dao::endpoint_srv6_config::EndpointSrv6ConfigDao>,
    ) -> Self {
        DefaultEndpointSrv6ConfigService {
            endpoint_srv6_config_dao,
        }
    }
}

#[async_trait::async_trait]
impl EndpointSrv6ConfigService for DefaultEndpointSrv6ConfigService {
    async fn add(
        &self,
        endpoint_id: &str,
        params: &dto::core::endpoint_srv6_config::AddParams,
    ) -> Result<(), Error> {
        if self
            .endpoint_srv6_config_dao
            .exists(dao::endpoint_srv6_config::Filter {
                endpoint_id: Some(endpoint_id.to_string()),
                ..Default::default()
            })
            .await?
        {
            return Err(Error::ResponseError(
                ResponseError::endpoint_srv6_config_already_exist(endpoint_id),
            ));
        }

        self.endpoint_srv6_config_dao
            .insert(po::endpoint_srv6_config::ActiveModel {
                id: Default::default(),
                endpoint_id: po::ActiveValue::Set(endpoint_id.to_string()),
                locator: po::ActiveValue::Set(params.locator.first_address().into()),
                locator_length: po::ActiveValue::Set(params.locator.network_length()),
                create_timestamp_millis: Default::default(),
                update_timestamp_millis: Default::default(),
            })
            .await?;

        Ok(())
    }

    async fn get(
        &self,
        endpoint_id: &str,
    ) -> Result<dto::core::endpoint_srv6_config::EndpointSrv6Config, Error> {
        let config = self
            .try_get(endpoint_id)
            .await?
            .ok_or(ResponseError::endpoint_srv6_config_not_found(endpoint_id))?;

        Ok(config)
    }

    async fn try_get(
        &self,
        endpoint_id: &str,
    ) -> Result<Option<dto::core::endpoint_srv6_config::EndpointSrv6Config>, Error> {
        let filter = dao::endpoint_srv6_config::Filter {
            endpoint_id: Some(endpoint_id.to_string()),
            ..Default::default()
        };

        if let Some(m) = self.endpoint_srv6_config_dao.try_get(filter).await? {
            return Ok(Some(m.try_into()?));
        }

        Ok(None)
    }

    async fn update(
        &self,
        endpoint_id: &str,
        params: &dto::core::endpoint_srv6_config::UpdateParams,
    ) -> Result<(), Error> {
        if params == &Default::default() {
            return Ok(());
        }

        let filter = dao::endpoint_srv6_config::Filter {
            endpoint_id: Some(endpoint_id.to_string()),
            ..Default::default()
        };

        if !self.endpoint_srv6_config_dao.exists(filter.clone()).await? {
            return Err(Error::ResponseError(
                ResponseError::endpoint_srv6_config_not_found(endpoint_id),
            ));
        }

        self.endpoint_srv6_config_dao
            .update(filter, params.into())
            .await?;

        Ok(())
    }

    async fn delete(&self, endpoint_id: &str) -> Result<(), Error> {
        let filter = dao::endpoint_srv6_config::Filter {
            endpoint_id: Some(endpoint_id.to_string()),
            ..Default::default()
        };
        self.endpoint_srv6_config_dao.delete(filter).await?;
        Ok(())
    }

    async fn exists(
        &self,
        filter: &dto::core::endpoint_srv6_config::Filter,
    ) -> Result<bool, Error> {
        let exists = self.endpoint_srv6_config_dao.exists(filter.into()).await?;
        Ok(exists)
    }
}

impl TryFrom<&po::endpoint_srv6_config::Model>
    for dto::core::endpoint_srv6_config::EndpointSrv6Config
{
    type Error = anyhow::Error;

    fn try_from(value: &po::endpoint_srv6_config::Model) -> Result<Self, Self::Error> {
        let locator =
            cidr::Ipv6Cidr::new(Ipv6Addr::try_from(&value.locator)?, value.locator_length)
                .map_err(anyhow::Error::new)?;

        Ok(Self { locator })
    }
}

impl TryFrom<po::endpoint_srv6_config::Model>
    for dto::core::endpoint_srv6_config::EndpointSrv6Config
{
    type Error = anyhow::Error;

    fn try_from(value: po::endpoint_srv6_config::Model) -> Result<Self, Self::Error> {
        Self::try_from(&value)
    }
}

impl From<&dto::core::endpoint_srv6_config::Filter> for dao::endpoint_srv6_config::Filter {
    fn from(value: &dto::core::endpoint_srv6_config::Filter) -> Self {
        let mut locator = None;
        let mut locator_length = None;
        if let Some(v) = &value.locator {
            locator = Some(v.first_address().into());
            locator_length = Some(v.network_length());
        }
        Self {
            endpoint_id: value.endpoint_id.clone(),
            locator,
            locator_length,
        }
    }
}

impl From<dto::core::endpoint_srv6_config::Filter> for dao::endpoint_srv6_config::Filter {
    fn from(value: dto::core::endpoint_srv6_config::Filter) -> Self {
        Self::from(&value)
    }
}

impl From<&dto::core::endpoint_srv6_config::UpdateParams>
    for dao::endpoint_srv6_config::UpdateParams
{
    fn from(value: &dto::core::endpoint_srv6_config::UpdateParams) -> Self {
        if value == &Default::default() {
            return Self::default();
        }

        let mut locator = po::ActiveValue::NotSet;
        let mut locator_length = po::ActiveValue::NotSet;

        if let Some(v) = &value.locator {
            locator = po::ActiveValue::Set(v.first_address().into());
            locator_length = po::ActiveValue::Set(v.network_length());
        }

        Self {
            locator,
            locator_length,
        }
    }
}
